package com.bianmaba.spring.security.basic.configuration;

import com.bianmaba.spring.security.basic.authentication.BianmabaAuthenticationEntryPoint;
import com.bianmaba.spring.security.basic.handler.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.*;
import org.springframework.security.web.header.writers.HstsHeaderWriter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static com.bianmaba.spring.security.autoconfig.WebSecurityAutoConfiguration.ORDER;

@Configuration
@ConditionalOnClass({WebSecurityConfigurerAdapter.class})
//如果此类加载前已经存在指定类型的实例，则此类将不会被实例化(如果有继承关系，则可能会执行构造函数，但实际为同一个实例时为正常，否则可能不正常)
@ConditionalOnMissingBean({WebSecurityConfigurerAdapter.class})
@Import({BianmabaSessionInformationExpiredStrategy.class, AjaxGlobalExceptionHandler.class, BianmabaAuthenticationFailureHandler.class, BianmabaAuthenticationSuccessHandler.class, BianmabaLogoutSuccessHandler.class, BianmabaAccessDeniedHandler.class})
@Order(ORDER)//配置初始化排序，此值越低，越靠前，但@Import引入的类此注解无效
public class BasicWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
    private static final Logger LOG = LoggerFactory.getLogger(BasicWebSecurityConfigurerAdapter.class);
    @Autowired
    protected SecurityPropertiesConfiguration.WebSecurityProperties webSecurityProperties;
    @Autowired
    protected BianmabaSessionInformationExpiredStrategy sessionInformationExpiredStrategy;
    @Autowired
    protected BianmabaAuthenticationFailureHandler authenticationFailureHandler;
    @Autowired
    protected BianmabaAuthenticationSuccessHandler authenticationSuccessHandler;
    @Autowired
    protected BianmabaLogoutSuccessHandler logoutSuccessHandler;
    @Autowired
    protected BianmabaAccessDeniedHandler accessDeniedHandler;


    public BasicWebSecurityConfigurerAdapter() {
        if (this.getClass().getName().equals(BasicWebSecurityConfigurerAdapter.class.getName())) ;
        {
            LOG.info("加载WEB安全性配置器适配器(order=" + ORDER + "):" + this.getClass().getName());
        }
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        //静态免验证地址（适合用于静态资源或页面）
        List<String> paths = webSecurityProperties.getIgnored();
        if (paths != null && !paths.isEmpty()) {
            List<RequestMatcher> matchers = new ArrayList<RequestMatcher>();
            if (!ObjectUtils.isEmpty(paths)) {
                for (String pattern : paths) {
                    matchers.add(new AntPathRequestMatcher(pattern, null));
                }
            }
            web.ignoring().requestMatchers(new OrRequestMatcher(matchers));
        }
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {


        //配置需要权限才能访问的资源
        configureResources(http);


        //permitAll必须在 http.authorizeRequests().anyRequest().authenticated()时才能使用
        if (webSecurityProperties.isAnyRequestAuthenticated()) {
            //免验证必须放在必验证前
            configurePermitAll(http);
        }
        initSessionManagement(http.sessionManagement());

        initLogout(http);
        initLogin(http);
//        //用户配置:spring.security.ajax为true时，登录，注销和session相关的操作结果将以json的方式返回到前端，否则将使用页面跳转
//        if (webSecurityProperties.isAjax()) {
//            //处理登录配置
        // loginForm.loginPage("/not_logon").loginProcessingUrl("/login").failureHandler(ajaxSecurityHandler).successHandler(new AjaxSavedRequestAwareAuthenticationSuccessHandler());
//            //处理注销配置
        //    logoutForm.logoutSuccessHandler(new BianmabaSessionInformationExpiredStrategy());
//            //处理没有权限时的配置
//
        if (!StringUtils.isEmpty(webSecurityProperties.getAccessDenied().getErrorPage())) {
            accessDeniedHandler.setErrorPage(webSecurityProperties.getAccessDenied().getErrorPage());
        }
        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);

//        } else {
//            if (!StringUtils.isEmpty(webSecurityProperties.getLoginPage())) {
//                loginForm.loginPage(webSecurityProperties.getLoginPage());
//            }
//            if (!StringUtils.isEmpty(webSecurityProperties.getLoginSuccessUrl())) {
//                loginForm.defaultSuccessUrl(webSecurityProperties.getLoginSuccessUrl());
//            }
//        }

        configureHeaders(http.headers(), webSecurityProperties.getHeaders());

        //配置是否使用SSL
        if (this.webSecurityProperties.isRequireSsl()) {
            http.requiresChannel().anyRequest().requiresSecure();
        }
        //判断是否打开CSRF
        if (!this.webSecurityProperties.isEnableCsrf()) {
            http.csrf().disable();
        }
        //处理用户配置的的必须登录才能访问的资源（当anyRequestAuthenticated为false生效）：spring.security.authenticated
        configureAuthenticated(http);
    }

    private void initSessionManagement(SessionManagementConfigurer<HttpSecurity> httpSecuritySessionManagementConfigurer) {
        SecurityPropertiesConfiguration.Session session = webSecurityProperties.getSession();
        if (session.getMaximumSessions() != null) {
         SessionManagementConfigurer<HttpSecurity>.ConcurrencyControlConfigurer sessionManagement=   httpSecuritySessionManagementConfigurer.maximumSessions(session.getMaximumSessions());
            //            //处理session配置
            sessionManagement.expiredSessionStrategy(sessionInformationExpiredStrategy)
                    .and().sessionAuthenticationFailureHandler(authenticationFailureHandler);
        }
    }

    private void initLogout(HttpSecurity http) throws Exception {
        LogoutConfigurer<HttpSecurity> logoutConfigurer = http.logout();
        SecurityPropertiesConfiguration.Logout logout = webSecurityProperties.getLogout();
        SecurityPropertiesConfiguration.LogoutSuccess success = logout.getSuccess();
        if (webSecurityProperties.isAnyRequestAuthenticated()) {
            if (logout.isPermitAll()) {
                logoutConfigurer.permitAll();
            }
        }
        if (!StringUtils.isEmpty(success.getDefaultTargetUrl())) {
            logoutSuccessHandler.setDefaultTargetUrl(success.getDefaultTargetUrl());
        }
        logoutSuccessHandler.setAlwaysUseDefaultTargetUrl(success.isAlwaysUseDefaultTargetUrl());
        logoutSuccessHandler.setUseReferer(success.isUseReferer());
        if (!StringUtils.isEmpty(success.getTargetUrlParameter())) {
            logoutSuccessHandler.setTargetUrlParameter(success.getTargetUrlParameter());
        }


        logoutConfigurer.logoutSuccessHandler(logoutSuccessHandler);
    }

    private void initLogin(HttpSecurity http) throws Exception {

        AbstractAuthenticationFilterConfigurer loginConfigurer = http.formLogin();
        if (webSecurityProperties.isAnyRequestAuthenticated()) {
            loginConfigurer.permitAll();
        }
        SecurityPropertiesConfiguration.Login login = webSecurityProperties.getLogin();
        String loginPageUrl = webSecurityProperties.getLogin().getLoginPage();
        if (StringUtils.isEmpty(loginPageUrl)) {
            loginPageUrl = "/login";
        } else {
            Method method = loginConfigurer.getClass().getMethod("loginPage", String.class);
            method.invoke(loginConfigurer, loginPageUrl);
        }
        BianmabaAuthenticationEntryPoint authenticationEntryPoint = new BianmabaAuthenticationEntryPoint(loginPageUrl);
        Field field = AbstractAuthenticationFilterConfigurer.class.getDeclaredField("authenticationEntryPoint");
        field.setAccessible(true);
        field.set(loginConfigurer, authenticationEntryPoint);


        if (!StringUtils.isEmpty(webSecurityProperties.getLogin().getSuccess().getDefaultTargetUrl())) {
            authenticationSuccessHandler.setDefaultTargetUrl(webSecurityProperties.getLogin().getSuccess().getDefaultTargetUrl());
        }
        authenticationSuccessHandler.setAlwaysUseDefaultTargetUrl(webSecurityProperties.getLogin().getSuccess().isAlwaysUseDefaultTargetUrl());

        loginConfigurer.successHandler(authenticationSuccessHandler);


        String defaultFailureUrl = webSecurityProperties.getLogin().getFailure().getDefaultFailureUrl();
        if (StringUtils.isEmpty(defaultFailureUrl)) {
            defaultFailureUrl = loginPageUrl + "?error";
        }
        authenticationFailureHandler.setDefaultFailureUrl(defaultFailureUrl);

        loginConfigurer.failureHandler(authenticationFailureHandler);
        field = AbstractAuthenticationFilterConfigurer.class.getDeclaredField("failureUrl");
        field.setAccessible(true);
        field.set(loginConfigurer, defaultFailureUrl);
    }

    public void configureHeaders(HeadersConfigurer<HttpSecurity> configurer, SecurityPropertiesConfiguration.Headers headers) throws Exception {
        if (headers.getHsts() != SecurityPropertiesConfiguration.Headers.HSTS.NONE) {
            boolean includeSubDomains = headers.getHsts() == SecurityPropertiesConfiguration.Headers.HSTS.ALL;
            HstsHeaderWriter writer = new HstsHeaderWriter(includeSubDomains);
            writer.setRequestMatcher(AnyRequestMatcher.INSTANCE);
            configurer.addHeaderWriter(writer);
        }

        if (!headers.isContentType()) {
            configurer.contentTypeOptions().disable();
        }

        if (StringUtils.hasText(headers.getContentSecurityPolicy())) {
            String policyDirectives = headers.getContentSecurityPolicy();
            SecurityPropertiesConfiguration.Headers.ContentSecurityPolicyMode mode = headers.getContentSecurityPolicyMode();
            if (mode == SecurityPropertiesConfiguration.Headers.ContentSecurityPolicyMode.DEFAULT) {
                configurer.contentSecurityPolicy(policyDirectives);
            } else {
                configurer.contentSecurityPolicy(policyDirectives).reportOnly();
            }
        }

        if (!headers.isXss()) {
            configurer.xssProtection().disable();
        }

        if (!headers.isCache()) {
            configurer.cacheControl().disable();
        }

        if (!headers.isFrame()) {
            configurer.frameOptions().disable();
        }

    }

    /**
     * 处理用户配置的的必须登录才能访问的资源
     * <pre>
     * 当anyRequestAuthenticated为false时，spring.security.authenticated生效，否则不生效，将配置所有资源访问都需要登录（除许可的和忽略的资源）
     * </pre>
     *
     * @param http
     * @throws Exception
     */
    protected void configureAuthenticated(HttpSecurity http) throws Exception {
        if (webSecurityProperties.isAnyRequestAuthenticated()) {
            http.authorizeRequests().anyRequest().authenticated();
        } else {
            List<String> authenticated = webSecurityProperties.getAuthenticated();
            if (authenticated != null && !authenticated.isEmpty()) {
                List<String> matchers = new ArrayList<String>();
                if (!ObjectUtils.isEmpty(authenticated)) {
                    for (String pattern : authenticated) {
                        matchers.add(pattern);
                    }
                }
                http.authorizeRequests().antMatchers(matchers.toArray(new String[matchers.size()])).authenticated();
                LOG.info("Permited:" + String.join(",", authenticated));
            }
        }
    }

    //处理用户配置的的资源：spring.security.resources
    protected void configureResources(HttpSecurity http) throws Exception {
        //配置需要权限访问的路径
        List<Map<String, String>> resources = webSecurityProperties.getResources();
        if (!resources.isEmpty()) {
            for (Map<String, String> resource : resources) {
                String path = resource.get("path");
                String[] roles = resource.get("roles").split("[,]");
                http.authorizeRequests().antMatchers(path).hasAnyRole(roles);
            }
        }
    }

    /**
     * 处理用户配置的的允许访问的资源：spring.security.permited
     * <pre>
     * permitAll，会给没有登录的用户适配一个AnonymousAuthenticationToken，设置到SecurityContextHolder，方便后面的filter可以统一处理authentication。
     * </pre>
     * * @param http
     *
     * @throws Exception
     */
    protected void configurePermitAll(HttpSecurity http) throws Exception {
        List<String> permited = webSecurityProperties.getPermited();
        if (permited != null && !permited.isEmpty()) {
            List<String> matchers = new ArrayList<String>();
            if (!ObjectUtils.isEmpty(permited)) {
                for (String pattern : permited) {
                    matchers.add(pattern);
                }
            }
            http.authorizeRequests().antMatchers(matchers.toArray(new String[matchers.size()])).permitAll();
            LOG.info("Permited:" + String.join(",", permited));
        }
    }
}
